# vim:ts=4:sw=4:noet:
""" Main window controller """

import wx
import time
import os
import logging
import webbrowser
from c.base.attribute import attribute
from c.base.support import Status, CoreException
from c.base.version import InfoStrings
from c.base.platform import Platform
from c.compiler.mem import Memory
from c.compiler.cmg import CMG
from c.compiler.compiler import Compiler
from c.compiler.spy import STATE_ERROR, STATE_COMPILED
from c.device.manager import DeviceManager
from c.remote.remote import Remote, ERROR_BAD_MSG, ERROR_RUNTIME
from c.ui.taskmanager import TaskManager
from c.ui.savestate import UISaveState
from c.ui.updater import Updater
from c.ui.projectcontroller import MethodItem
from c.ui.wxw.events import *

class UILogger:
	def __init__(self, ui):
		self.ui = ui

	def write(self, msg):
		self.ui.logMessage(msg)

################################################################################
#                                                                              #
################################################################################

class MainController:

	view = attribute("_view", "r")

	def __init__(self, view, args):
		self._view = view
		self._args = args
		
		self.stat = Status({"exceptions" : True}, outstream=self._view.uilog)
		self.mem = Memory(self.stat)
		self.mem.loadDefaultDate(Platform.getDataFilePath("default.cfg"))
		self.compiler = Compiler(self.mem)

		self.tm = TaskManager(self)
		self.dm = DeviceManager(self)
		self.r = Remote(self.dm, self.stat)
		self.botRunning = False
		self.saveState = UISaveState()
		self.projectFilename = self.saveState.getOpt("lastproj", "")
		self.defaultDir = self.saveState.getOpt("defaultDir", Platform.getDefaultDir())
		self.firstSave = False
		self.latestImageSize = -1
		self.compiledState = {}
		logging.info("base path: %s" % Platform.getBasePath())

	def onStartComplete(self):
		self.dm.start()
		if len(self._args)>1 and os.path.exists(self._args[1]):
			self.projectFilename = self._args[1]
			self.loadProject()
			self._view.setButtonStates(1,1,0)
		elif self.projectFilename and os.path.exists(self.projectFilename):
			self.loadProject()
			self._view.setButtonStates(1,1,0)
		else:
			self.projectFilename = ""
		self.updater = Updater(InfoStrings["versionString"], self.updateAvailable)
		doup = True #self.saveState.getOpt("updateOnStart")
		if doup:
			self.updater.check()
			self.saveState.setOpt("updateOnStart", True)
		elif doup==None:
			self.saveState.setOpt("updateOnStart", True)

	def onShutdown(self):
		self.saveState.save()
	
	def onEditLibraryButton(self, editable):
		self.saveState.setOpt("editablelibrary", editable)

	def onAutoSwitchButton(self, autoswitch):
		self.saveState.setOpt("autoswitch", autoswitch)

	def checkProjectUpgrade(self):
		if self.mem.oldImage:
			self._view.onProjectUpgrade()

	def newProject(self, name, mainclass=None):
		self.mem.load(Platform.getDataFilePath("default.%s" % InfoStrings["imageExtension"]))
		if mainclass:
			self._view.project.controller.createNewClass(name, mainclass)
			self._view.project.controller.createNewMethod("main",name)
		self._view.project.controller.populate()
		self._view.project.controller.jumpTo("%s>>main" % name.lower())

	def closeProject(self):
		self.projectFilename = ""
		
	def openProject(self, fn):
		if self.projectFilename!="":
			self.closeProject()
		self.loadProject(fn)
		self._view.project.controller.populate()
		mainCls = self.mem.getMainClass()
		if mainCls:
			self._view.project.controller.jumpTo("%s>>main" % mainCls.name.lower())
		else:
			self._view.setEmptyView()
			self._view.srctext.setText("")
			self._view.srctext.Enable(False)
		self.dm.refresh()
		self.firstSave = False
		self._view.setButtonStates(1, 1, 0)

	def loadProject(self, filename=None):
		if filename:
			self.projectFilename = filename
		try:
			self.mem.load(self.projectFilename)
		except Exception,e:
			self._view.onLoadFailed(e)
			return
		self._view.project.controller.populate()
		if filename:
			self.checkProjectUpgrade()
		self.saveState.setOpt("lastproj", self.projectFilename)
		self.dm.refresh()
		self._view.updateTitle()

	def saveProject(self):
		self.saveState.setOpt("lastproj", self.projectFilename)
		self._view.project.controller.refreshCurrentItem()
		self.mem.save(self.projectFilename)
		self._view.setStatusText("Project saved to %s" % self.projectFilename)
		self._view.project.controller.changed = False
		self._view.updateTitle()

	def checkBotState(self, args):
		res = self.r.getVMState()
		if not res or len(res)<2:
			return
		if res[1] == 2:
			err = self.r.getLastError()
			ename = err[2]
			eval = str(err[1])
			if err[0] == ERROR_BAD_MSG:
				eval = "'%s'" % self.r.getObject(err[1]).asString()
			elif err[0] == ERROR_RUNTIME:
				eobj = self.r.getObject(err[1])
				ecls = self.r.getObject(eobj.cls)
				eclsName = self.r.getObject(ecls.data[0]).asString()
				if eclsName in ["String", "Symbol"]:
					eval = eobj.asString()
				else:
					eval = eobj.asPrettyString(self.r)
			errormsg = "Error: %s (%s)\n" % (ename,eval)
			self._view.logMessage(errormsg)
			return errormsg
		else:
			raise CoreException("Bad bot state %s" % res)

	def isLibraryEditable(self):
		return self.saveState.getOpt("editablelibrary", False)

	def shouldAutoSwitch(self):
		return self.saveState.getOpt("autoswitch", True)

	def updateAvailable(self, update):
		self._view.postEvent(UiStateEvent("update-available", update))

	def deleteMethod(self, m):
		if m in self.compiledState:
			del self.compiledState[m]

	def openHTMLDocs(self, name):
		url = "file://"+os.path.join(Platform.getDataPath(), "tutorial", name)
		webbrowser.open(url)

	##############################################################################

	def showDeviceUpgrade(self, newver, filename):
		self._view.postEvent(DeviceUpgradeEvent((newver, filename)))

	def addDevice(self, id, name, conn):
		self._view.postEvent(DeviceChangeEvent(id, name, conn))

	def removeDevice(self, id):
		self._view.postEvent(DeviceChangeEvent(id))

	def handleThreadException(self, excp):
		if wx:
			self._view.postEvent(ThreadExceptionEvent(excp))

	##############################################################################
	
	def doCompile(self, item, source, runAfter):
		self.tm.createTask("compile", self.__taskFuncCompile, (item, source, runAfter))

	def doRun(self, postRunFunc=None):
		self.botRunning = True
		self.tm.createTask("run", self.__taskFuncRun, [postRunFunc])

	def doStop(self):
		self.botRunning = False
		self.tm.stopTask("run")
		self.r.setVMState(0)
		self._view.setButtonStates(1,1,0)

	def doImageLoad(self):
		self.tm.createTask("load", self.__taskFuncLoad)

	def doUpgradeStart(self):
		pass

	def doUpgrade(self, fn):
		self.tm.createTask("upgrade", self.__taskFuncUpgrade, [fn])

	##############################################################################
	
	def __taskFuncUpgrade(self,fn):
		def progressCb(percent): pass
			#if (percent%10)==0:
			#	self._view.postEvent(UiStateEvent("progress-update",percent))
		self._view.logClear()
		self._view.postEvent(UiStateEvent("button-msg", (0,0,0,1,"Upgrading robot firmware ...")))
		timeStart = time.time()
		try:
			self.r.upgrade(fn, progressCb)
			timeEnd = time.time()
			self._view.logMessage("Completed in %0.2fs\n" % (timeEnd - timeStart))
		except Exception,e:
			self._view.logMessage("Upgrade failed: %s\n" % e)
			self._view.postEvent(UiStateEvent("button-msg", (0,1,0,1, "Upgrade failed: %s" % e)))
			return False
	
	def __taskFuncLoad(self):
		def progressCb(percent): pass
			#if (percent%10)==0:
			#	self._view.postEvent(UiStateEvent("progress-update", percent))
		for meth in self.compiledState:
			state, errdata = self.compiledState[meth]
			if state==STATE_ERROR:
				self._view.postEvent(UiStateEvent("button-msg", (1,1,0,1, "Can't run, error in project")))
				self._view.project.controller.showSource(meth.incls.name,meth.name,0)
				self._view.srctext.errorLine(errdata)
				return False
		self._view.logClear()
		self._view.postEvent(UiStateEvent("button-msg", (0,0,0,1,"Uploading image to robot ...")))
		timeStart = time.time()
		compileOk = self.mem.readyForSave()
		if not compileOk:
			self._view.logMessage("Image generation failed: no main method found\n")
			self._view.postEvent(UiStateEvent("button-msg",
				(1,1,0,1, "Image generation failed: no main method found")))
			return False
		img = CMG(self.mem)
		imgbuf = img.saveToBuf()
		self.latestImageSize = img.imageSize
		self._view.logMessage("%d/24Kb (%d%%) used\n" % (
			(img.imageSize/1024),int(img.imageSize)/(24.0*1024)*100.0))
		try:
			self.r.uploadCMG(imgbuf, progressCb)
			timeEnd = time.time()
			self._view.logMessage("Completed in %0.2fs\n" % (timeEnd - timeStart))
		except Exception,e:
			self._view.logMessage("Upload failed: %s\n" % e)
			self._view.postEvent(UiStateEvent("button-msg", (0,1,0,1, "Upload failed: %s" % e)))
			return False
		self.doRun()

	def __taskFuncRun(self, postRunFunc):
		self._view.postEvent(UiStateEvent("start", None))
		self._view.postEvent(UiStateEvent("button-msg", (0,0,1,1, "Running ...")))
		if self.shouldAutoSwitch():
			self._view.postEvent(UiStateEvent("change-view", "sim"))
		self.r.setVMState(1)
		while self.botRunning:
			res = self.r.getVMState()
			if not res or len(res)<2:
				break
			if (res[1]!=1):
				if res[1]!=0:
					self._view.postEvent(UiStateEvent("trace", "Runtime Error"))
				break
			time.sleep(0.01)
		self.botRunning = False
		if postRunFunc:
			self._view.postEvent(UiStateEvent("post-run", postRunFunc))
		self._view.postEvent(UiStateEvent("button-msg", (1,1,0,1,"")))
		
	def __taskFuncCompile(self, item, source, runAfter=False):
		self._view.postEvent(UiStateEvent("button", (0,0,0,1)))
		method = item.obj
		self._view.logClear()
		timeStart = time.time()
		self._view.srctext.errorClear()
		try:
			c = self.compiler
			c.compileMethod(method.incls, source)
		except CoreException, e:
			self._view.postEvent(UiStateEvent("button-msg", (1,1,0,1, "Check failed: %s" % e)))
			self._view.logMessage("Check of %s in %s failed:\n" % (
					method.name, method.incls.name))
			self._view.logMessage("%s\n" % e)
			selItem = self._view.project.controller.getSelectedItem()
			if isinstance(selItem, MethodItem) and selItem.obj == method and e.data:
				self._view.srctext.errorLine(e.data)
			method.state = STATE_ERROR
			self.compiledState[method] = (method.state, e.data)
			self._view.postEvent(CompileEvent((item, 0)))
			return False
		except Exception, e:
			self._view.postEvent(UiStateEvent("button-msg", (1,1,0,1, "Fatal compile error: %s" % e)))
			self._view.logMessage("Compilation of %s in %s failed fatally:\n" % (
					method.name, method.incls.name))
			self._view.logMessage("%s\n" % e)
			method.state = STATE_ERROR
			self.compiledState[method] = (method.state, None)
			self._view.postEvent(CompileEvent((item, 0)))
			return False
		nbytes = len(method.bytecodes)
		if not runAfter:
			self._view.postEvent(UiStateEvent("button-msg", (1,1,0,1, "Check OK")))
		self._view.project.controller.changed = True
		msg = "Check of %s in %s succeeded\n" % (
			method.name, method.incls.name)
		self._view.logMessage(msg)
		self._view.logMessage("%d bytes used\n" % nbytes)
		method.state = STATE_COMPILED
		self.compiledState[method] = (method.state, None)
		self._view.postEvent(CompileEvent((item, 1)))
		time.sleep(0.1)
		if runAfter:
			self.doImageLoad()
		return True

